/*
 * Copyright (C) 2013 Wojciech Dzierżanowski
 * See LICENSE.txt for licensing details.
 */

package wdzierzan.downstream.core;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Wojciech Dzierzanowski <wojciech.dzierzanowski@gmail.com>
 */
public class Receiver {
    
    private static final Logger logger = Logger.getLogger(Receiver.class.getName());

    private final ServerSocketChannel server;
    private ProgressReport progressReport;

    public Receiver(int port) throws IOException {
        server = ServerSocketChannel.open();
        server.socket().bind(new InetSocketAddress(port));
    }

    private void close() {
        if (server != null)
            try {
                server.close();
                logger.log(Level.INFO, "Stopped listening on {0}", server);
            } catch (IOException ex) {
                logger.log(Level.SEVERE, "Could not close socket", ex);
            }
    }

    public void setProgressReport(ProgressReport progressReport) {
        this.progressReport = progressReport;
    }

    public void saveAs(String destPath) throws IOException {

        if (server == null)
            throw new IllegalStateException("Not listening");

        logger.log(Level.INFO, "Listening on {0}", server);
        SocketChannel client = null;
        FileChannel output = null;
        try {
            client = server.accept();
            logger.log(Level.INFO, "Accepted connection from {0}", client);
            
            output = new FileOutputStream(destPath).getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(16 * 1024);

            long startTime = System.nanoTime();
            long bytes = 0;
            long read = 0;

            while ((read = client.read(buffer)) != -1) {

                buffer.flip();
                output.write(buffer);
                buffer.clear();

                if (progressReport == null)
                    continue;

                bytes += read;
                final long endTime = System.nanoTime();
                if (endTime - startTime >= progressReport.getRateUpdatePeriod() * 1000 * 1000) {
                    progressReport.updateRate(
                            (int) (bytes * 1000 * 1000 * 1000 / ((endTime - startTime) * 1024)));
                    startTime = endTime;
                    bytes = 0;
                }
            }
        } catch (IOException e) {
            logger.log(Level.SEVERE, "Interrupted", e);
            throw e;
        } catch (RuntimeException e) {
            logger.log(Level.SEVERE, "Interrupted", e);
            throw e;
        } finally {
            if (client != null)
                client.close();
            if (output != null)
                output.close();
            close();
        }
    }
}
